package buildinjava.installer;

import buildinjava.BuildException;
import buildinjava.io.Files;
import buildinjava.io.FileFilters;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Builds an installer using NSIS.
 *
 * @author Gili Tzabari
 * @see http://nsis.sourceforge.net/Main_Page
 */
public final class NSIS
{
	/**
	 * Sets the installer script.
	 *
	 * @param script the installer script
	 * @return a HaveScript object
	 * @throws IllegalArgumentException if script is null
	 */
	public HaveScript script(File script) throws IllegalArgumentException
	{
		if (script == null)
			throw new IllegalArgumentException("script may not be null");
		if (!script.isFile())
			throw new IllegalArgumentException("script file must exist: " + script.getAbsolutePath());
		return new HaveScript(script);
	}

	/**
	 * Configured with a script directory.
	 *
	 * @author Gili Tzabari
	 */
	public static final class HaveScript
	{
		private final File script;

		/**
		 * Creates a new HaveScript object.
		 *
		 * @param script the script directory
		 */
		public HaveScript(File script)
		{
			this.script = script;
		}

		/**
		 * Sets the source directory.
		 *
		 * @param source the source directory
		 * @return a HaveSource object
		 * @throws IllegalArgumentException if source is null
		 */
		public HaveSource source(File source) throws IllegalArgumentException
		{
			if (source == null)
				throw new IllegalArgumentException("source may not be null");
			if (!source.isDirectory())
				throw new IllegalArgumentException("source directory must exist: "
																					 + source.getAbsolutePath());
			return new HaveSource(script, source);
		}
	}

	/**
	 * Configured with a script file and source directory.
	 *
	 * @author Gili Tzabari
	 */
	public static final class HaveSource
	{
		private final File script;
		private final File source;

		/**
		 * Creates a new HaveSource object.
		 *
		 * @param script the script file
		 * @param source the source directory
		 */
		public HaveSource(File script, File source)
		{
			this.script = script;
			this.source = source;
		}

		/**
		 * Sets the target directory.
		 *
		 * @param target the target directory
		 * @return a Builder object
		 * @throws IllegalArgumentException if target is null
		 */
		public Builder target(File target) throws IllegalArgumentException
		{
			if (target == null)
				throw new IllegalArgumentException("target may not be null");
			return new Builder(script, source, target);
		}
	}

	/**
	 * Configured with a source and target directory.
	 *
	 * @author Gili Tzabari
	 */
	public static final class Builder
	{
		private final Logger log = LoggerFactory.getLogger(NSIS.class);
		private final File script;
		private final File source;
		private final File target;

		/**
		 * Creates a new Builder.
		 *
		 * @param script the script file
		 * @param source the source directory
		 * @param target the target directory
		 */
		public Builder(File script, File source, File target)
		{
			this.script = script;
			this.source = source;
			this.target = target;
		}

		/**
		 * Builds the installer.
		 */
		public void build()
		{
			log.debug("{}", toString());
			if (!target.exists() && !target.mkdirs())
				throw new BuildException("Cannot create " + target.getAbsolutePath());
			long targetTime = 0;
			if (target.exists())
			{
				for (File file: Files.list(target, FileFilters.isDirectory(), FileFilters.acceptAll()))
				{
					if (file.lastModified() > targetTime)
						targetTime = file.lastModified();
				}
			}

			long sourceTime = 0;
			for (File file: Files.list(source, FileFilters.isDirectory(), FileFilters.acceptAll()))
			{
				if (file.lastModified() > sourceTime)
					sourceTime = file.lastModified();
			}

			if (script.lastModified() <= targetTime && sourceTime <= targetTime)
			{
				if (log.isDebugEnabled())
				{
					log.debug("scriptTime=" + script.lastModified() + ", sourceTime=" + sourceTime
										+ ", targetTime="
										+ targetTime);
				}
				log.trace("Input files unchanged");
				return;
			}

			ProcessBuilder processBuilder = new ProcessBuilder(
				"C:/Program Files (x86)/NSIS/makensis.exe",
				"/DBUILD_VERSION=\"1.0\"",
				"/DBUILD_PLATFORM=\"i386\"",
				"/DINPUT_DIR=\"" + source.getPath() + "\"",
				"/DOUTPUT_DIR=\"" + target.getPath() + "\"",
				"/XSetCompress off", "/XCRCCheck off", script.getPath());
			processBuilder.redirectErrorStream(true);
			try
			{
				Process process = processBuilder.start();
				BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
				try
				{
					while (true)
					{
						String text = in.readLine();
						if (text == null)
							break;
						System.out.println(text);
					}
					int rc = process.waitFor();
					if (rc != 0)
						throw new BuildException("NSIS failed with return-code " + rc);
				}
				finally
				{
					in.close();
				}
			}
			catch (InterruptedException e)
			{
				throw new BuildException(e);
			}
			catch (IOException e)
			{
				throw new BuildException(e);
			}
		}

		@Override
		public String toString()
		{
			return getClass().getName() + "[script=" + script + ", source=" + source + ", target="
						 + target + "]";
		}
	}
}
